Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert
- entities.geojson — Schulen, deren Geokoordinaten und Attribute: entity_id, capacity, und andere Attribute
- entities.csv — Schulen und optimierungsrelevante Attribute: entity_id, capacity
- units.geojson — Statistische Blöcke Berlins, deren Geometrie und Attribute
- units.csv — Statsitische Blöcke Berlins und optimierungsrelevante Attribute: unit_id, population, percentage_sgb
- weights.csv — optimierungsrelevante Gewichte wie Fußwege / Spalten: entity_id, unit_id, weight, value
- assignment.csv — eine initiale Zuordnung / Spalten: unit_id, entity_id
Bereits in anderen scripten wurde vorbereitet:
- Fußwegen von einer großen Sichprobe von (Wohn-)Gebäuden zu allen Schulen wurden berechnet und in
route_matrix.csv gespeichert
- Die Stichprobe wurde in
sampled_buildings.csv gespeichert
Die Daten werden wiefolgt vorbereitet:
- pro Block werden die Anzahl der einzuschulenden Kinder mit Hilfe der Einwohnerzahlen nach Alter auf LOR-Ebene in
EWR201512E_Matrix.csv hochgerechnet
- Kinder des LOR werden Anteilig nach Einwohnerzahl des Blocks im Verhältnis zum LOR auf die Blöcke verteilt
- es werden minimale, durchschnittliche und maximale Fußwege aus jedem Block errechnet
TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)
Laden der Daten
sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields
HKO_2015 = readOGR('output/HKO_2015.geojson', 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "output/HKO_2015.geojson", layer: "OGRGeoJSON"
with 306019 features
It has 4 fields
Schulwege
route_matrix = read_rds('output/route_matrix.rds')
Schulkapazitäten und Einwohnerzahler auf LOR-Ebene
kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
Bezirk = col_character(),
Schulnummer = col_character(),
Schulname = col_character(),
Plätze = col_integer(),
Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
.default = col_character(),
ZEIT = col_integer(),
STADTRAUM = col_integer(),
E_E = col_number(),
E_EM = col_number(),
E_EW = col_number(),
E_E50_55 = col_number(),
E_E25U55 = col_number(),
E_E55U65 = col_number(),
E_E65U80 = col_number()
)
See spec(...) for full column specifications.
Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten
re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2) %>% cbind(over(re_schulstand, lor))
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"
Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?
re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"
Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:
bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
[1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
[9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01
07G02
07G03
07G05
07G06
07G07
07G10
07G12
07G13
07G14
07G15
07G16
07G17
07G18
07G19
07G20
07G21
07G22
07G23
07G24
07G25
07G26
07G27
07G28
07G29
07G30
07G31
07G32
07G34
07G35
07G36
07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))
Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.
data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):
relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
[1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
[9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01
07G02
07G03
07G05
07G06
07G07
07G10
07G12
07G13
07G14
07G15
07G16
07G17
07G18
07G19
07G20
07G21
07G22
07G23
07G24
07G25
07G26
07G27
07G28
07G29
07G30
07G31
07G32
07G34
07G35
07G36
07G37
Mapping Bezirk->LOR->Block
df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)
Sanity-Check: LORs und Blöcke im Bezirk
bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen
Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.
TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?
Plot der 6-Jährigen nach EWR201512E_Matrix.csv
Wir verwenden das mittel der 5- und 6-Jährigen.
TODO neue Daten von Torres? TODO Prognose?
relevant_ewr = einwohner_lor %>%
select(RAUMID, E_E05_06, E_E06_07) %>%
filter(RAUMID %in% relevant_lors$PLR) %>%
# Schnitt der 5 und 6-Jährigen
mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()
data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote
Strukturquote = 0.9
kids_in_blks = relevant_blks %>%
group_by(PLR) %>%
mutate(EinwRatio = Einw/sum(Einw)) %>%
ungroup %>%
left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
mutate(kids = EinwRatio*kids) %>%
mutate(kids = Strukturquote*kids) %>% # Strukturquote
select(BEZ, PLR, BLK, Einw, kids) %>%
as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze
relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer
Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken
'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912
Schulwege von Blöcken zu Schulen aggregieren
Für jedes Wohngebäude suchen wir den zugehörigen Block
residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)
Plot der relevanten Blöcke (mit Wohngebäuden)
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
#geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks
Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012 33
travel_matrix
Sozioökonomische Daten
Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes
UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')
ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections
wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == bez_id,]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)
gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
data.frame(area=intersection_area) # FIXME how else to normalize?
)
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)
Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?
sgbII_blk = intersection_data %>%
group_by(BLK) %>%
summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken
row.names(blk_in_bez) = blk_in_bez$BLK
buffeded_blk_in_bez = gBuffer(blk_in_bez, byid=T, width=40)
adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)
rownames(adjacency) = blk_in_bez$BLK
colnames(adjacency) = blk_in_bez$BLK
adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=rownames(.)) %>%
gather(to, connected, -from) %>% filter(connected) %>%
inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=rownames(.))) %>%
inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=rownames(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) +
geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
theme_nothing() + coord_map()
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property
instead

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image
adjacency_df %>% filter(connected & from != to) %>% select(from, to) %>% write_csv('app/data/adjacency.csv')
Select relevant data
optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)
optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]
dim(optim_matrix)
[1] 1012 32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2611.995
Naive Zuordnung: Jeder Block zur nächsten Schule
solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup
optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 780466.5
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle
library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
group_by(school) %>% summarise(
kids=sum(kids),
num_blocks=n(),
min_dist=min(min),
avg_dist=mean((kids*avg)/sum(kids)),
max_dist=max(max)
) %>%
inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
mutate(
utilization=kids/Kapa
) %>% select(
Schule=school,
`Blöcke`=num_blocks,
Kapazität=Kapa,
Kinder=kids,
Auslastung=utilization,
`Weg (min)`=min_dist,
`Weg (Ø)`=avg_dist,
`Weg (max)`=max_dist
) %>%
formattable(
list(
Kinder = formatter("span", x ~ digits(x, 2)),
Auslastung = formatter("span",
style = x ~ style(color = ifelse(x < 1, "green", "red")),
x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
),
`Weg (Ø)` = proportion_bar("lightblue"),
`Weg (min)` = proportion_bar("lightblue"),
`Weg (max)` = proportion_bar("lightblue")
)
)
|
Schule
|
Blöcke
|
Kapazität
|
Kinder
|
Auslastung
|
Weg (min)
|
Weg (Ø)
|
Weg (max)
|
|
07G01
|
12
|
66
|
62.12
|
94.13%
|
224.845734
|
676.0297
|
1433.0243
|
|
07G02
|
20
|
98
|
47.57
|
48.54%
|
46.556602
|
422.7953
|
792.4623
|
|
07G03
|
11
|
73
|
47.23
|
64.70%
|
66.549644
|
430.1879
|
1106.3140
|
|
07G05
|
21
|
78
|
98.13
|
125.81%
|
56.170158
|
567.2555
|
965.2747
|
|
07G06
|
27
|
55
|
72.94
|
132.62%
|
236.302841
|
789.0100
|
1370.8586
|
|
07G07
|
12
|
81
|
20.70
|
25.56%
|
231.677170
|
711.5683
|
1572.3916
|
|
07G10
|
30
|
112
|
141.12
|
126.00%
|
47.832695
|
640.2891
|
1527.4636
|
|
07G12
|
13
|
75
|
35.51
|
47.34%
|
84.875656
|
354.5439
|
623.3419
|
|
07G13
|
27
|
82
|
130.38
|
159.00%
|
83.382767
|
494.2252
|
957.9329
|
|
07G14
|
33
|
78
|
80.83
|
103.63%
|
76.489960
|
422.2012
|
743.9302
|
|
07G15
|
53
|
104
|
218.74
|
210.32%
|
139.206421
|
835.4126
|
1650.3367
|
|
07G16
|
17
|
104
|
47.33
|
45.51%
|
135.925201
|
525.0121
|
912.3560
|
|
07G17
|
25
|
100
|
70.31
|
70.31%
|
51.462997
|
398.1707
|
710.3102
|
|
07G18
|
20
|
44
|
62.14
|
141.22%
|
97.120125
|
329.6189
|
720.3619
|
|
07G19
|
34
|
112
|
105.22
|
93.95%
|
60.016224
|
1117.3621
|
2279.4165
|
|
07G20
|
46
|
81
|
155.92
|
192.49%
|
98.167419
|
675.0707
|
1516.0098
|
|
07G21
|
21
|
72
|
55.20
|
76.67%
|
117.613678
|
487.9516
|
1200.9553
|
|
07G22
|
28
|
91
|
78.15
|
85.88%
|
123.256531
|
600.7208
|
1346.9137
|
|
07G23
|
10
|
50
|
23.72
|
47.44%
|
157.713531
|
678.1085
|
1193.4146
|
|
07G24
|
27
|
75
|
81.83
|
109.10%
|
67.979126
|
620.2428
|
1933.3115
|
|
07G25
|
35
|
75
|
112.70
|
150.26%
|
52.787018
|
622.3285
|
1279.9268
|
|
07G26
|
24
|
52
|
33.95
|
65.28%
|
77.638962
|
527.9525
|
1086.9845
|
|
07G27
|
17
|
75
|
42.11
|
56.15%
|
133.304367
|
650.2435
|
1366.7585
|
|
07G28
|
62
|
75
|
84.14
|
112.18%
|
137.133423
|
982.3003
|
1822.0323
|
|
07G29
|
95
|
104
|
115.72
|
111.27%
|
64.338501
|
940.4776
|
2020.5684
|
|
07G30
|
41
|
72
|
69.50
|
96.53%
|
82.527321
|
860.1026
|
2409.7322
|
|
07G31
|
20
|
78
|
47.49
|
60.88%
|
260.135132
|
883.8032
|
1464.1348
|
|
07G32
|
63
|
78
|
61.94
|
79.41%
|
42.085312
|
757.1401
|
1336.4922
|
|
07G34
|
40
|
100
|
176.64
|
176.64%
|
35.297066
|
1174.1653
|
2227.6230
|
|
07G35
|
35
|
75
|
84.89
|
113.18%
|
87.235146
|
771.1714
|
1455.3230
|
|
07G36
|
34
|
100
|
56.78
|
56.78%
|
192.735321
|
965.8743
|
2366.5962
|
|
07G37
|
59
|
69
|
91.06
|
131.97%
|
5.178658
|
1249.8005
|
2133.0967
|
Daten für die App speichern
- entities.geojson
- entities.csv
- units.geojson
- units.csv
- weights.csv
- assignment.csv
Neue Daten
Entities / Schulen
entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
rename(entity_id = spatial_name) %>%
select(-gml_id, -spatial_alias, -spatial_type) %>%
inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')
Units / Statistische Blöcke
units = subset(blk, BEZ == bez_id)
units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL
units = units %>% sp::merge(
optim_kids_in_blks %>%
left_join(sgbII_blk) %>%
select(unit_id=BLK, population=kids, sgbIIu65)
)
Joining, by = "BLK"
file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="units", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')
Zuordnung
assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')
Weights / Schulwege
travel_from_blks %>%
rename(unit_id=BLK, entity_id=dst) %>%
#gather(weight, value, -unit_id, -entity_id) %>%
write_csv('app/data/weights.csv')
Zusätzliche Daten
file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
addresses_in_bez = HKO_2015[!is.na(over(HKO_2015, subset(blk, BEZ == bez_id))$BEZ),c('STN', 'HNR')]
names(addresses_in_bez) = c('street', 'no')
addresses_in_bez@data
file.remove('app/data/addresses.geojson')
[1] TRUE
writeOGR(addresses_in_bez, "app/data/addresses.geojson", layer="addresses", driver="GeoJSON", check_exists = FALSE)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IFRSVUUKICAgIHRvY19mbG9hdDogVFJVRQotLS0KCmBgYHtyIGxpYnMsIGluY2x1ZGU9Riwgd2FybmluZz1GfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KG1hcHRvb2xzKQpsaWJyYXJ5KHJnZW9zKQpgYGAKCkRpZXNlcyBOb3RlYm9vayBiZXJlaXRldCBkaWUgRGF0ZW4gZsO8ciBkaWUgSW50ZWxsaWdlbnQgWm9uaW5nIEVuZ2luZSB2b3IuIEVzIHNwZWljaGVydAoKLSBlbnRpdGllcy5nZW9qc29uIOKAlCBTY2h1bGVuLCBkZXJlbiBHZW9rb29yZGluYXRlbiB1bmQgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5LCB1bmQgYW5kZXJlIEF0dHJpYnV0ZQotIGVudGl0aWVzLmNzdiDigJQgU2NodWxlbiB1bmQgb3B0aW1pZXJ1bmdzcmVsZXZhbnRlIEF0dHJpYnV0ZTogZW50aXR5X2lkLCBjYXBhY2l0eQotIHVuaXRzLmdlb2pzb24g4oCUIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlIEJlcmxpbnMsIGRlcmVuIEdlb21ldHJpZSB1bmQgQXR0cmlidXRlCi0gdW5pdHMuY3N2IOKAlCBTdGF0c2l0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiB1bml0X2lkLCBwb3B1bGF0aW9uLCBwZXJjZW50YWdlX3NnYgotIHdlaWdodHMuY3N2IOKAlCBvcHRpbWllcnVuZ3NyZWxldmFudGUgR2V3aWNodGUgd2llIEZ1w593ZWdlIC8gU3BhbHRlbjogZW50aXR5X2lkLCB1bml0X2lkLCB3ZWlnaHQsIHZhbHVlCi0gYXNzaWdubWVudC5jc3Yg4oCUIGVpbmUgaW5pdGlhbGUgWnVvcmRudW5nIC8gU3BhbHRlbjogdW5pdF9pZCwgZW50aXR5X2lkCgpCZXJlaXRzIGluIGFuZGVyZW4gc2NyaXB0ZW4gd3VyZGUgdm9yYmVyZWl0ZXQ6CgotIEZ1w593ZWdlbiB2b24gZWluZXIgZ3Jvw59lbiBTaWNocHJvYmUgdm9uIChfV29obl8tKUdlYsOkdWRlbiB6dSBhbGxlbiBTY2h1bGVuIHd1cmRlbiBiZXJlY2huZXQgdW5kIGluIGByb3V0ZV9tYXRyaXguY3N2YCBnZXNwZWljaGVydAotIERpZSBTdGljaHByb2JlIHd1cmRlIGluIGBzYW1wbGVkX2J1aWxkaW5ncy5jc3ZgIGdlc3BlaWNoZXJ0CgpEaWUgRGF0ZW4gd2VyZGVuIHdpZWZvbGd0IHZvcmJlcmVpdGV0OgoKLSBwcm8gQmxvY2sgd2VyZGVuIGRpZSBBbnphaGwgZGVyIGVpbnp1c2NodWxlbmRlbiBLaW5kZXIgbWl0IEhpbGZlIGRlciBFaW53b2huZXJ6YWhsZW4gbmFjaCBBbHRlciBhdWYgTE9SLUViZW5lIGluIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgIGhvY2hnZXJlY2huZXQKICAgIC0gS2luZGVyIGRlcyBMT1Igd2VyZGVuIEFudGVpbGlnIG5hY2ggRWlud29obmVyemFobCBkZXMgQmxvY2tzIGltIFZlcmjDpGx0bmlzIHp1bSBMT1IgYXVmIGRpZSBCbMO2Y2tlIHZlcnRlaWx0Ci0gZXMgd2VyZGVuIG1pbmltYWxlLCBkdXJjaHNjaG5pdHRsaWNoZSB1bmQgbWF4aW1hbGUgRnXDn3dlZ2UgYXVzIGplZGVtIEJsb2NrIGVycmVjaG5ldAoKVE9ETzoKLSBkaWUgc296aW/Dtmtvbm9taXNjaGVuIEZha3RvcmVuIHdlcmRlbiBhdXMgZGVuIFdhaGxiZXppcmtlbiBhdWYgZGllIEJsw7Zja2UgaG9jaGdlcmVjaG5ldCAoaHR0cHM6Ly9naXRodWIuY29tL2JlcmxpbmVybW9yZ2VucG9zdC9jb2dyYW4pCgojIyBMYWRlbiBkZXIgRGF0ZW4KCmBgYHtyIGxvYWQgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc2FtcGxlZF9idWlsZGluZ3MgPSByZWFkX3Jkcygnb3V0cHV0L3NhbXBsZWRfYnVpbGRpbmdzLnJkcycpCmJleiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYmxrID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JMS18yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpsb3IgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfTE9SXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnJlX3NjaHVsc3RhbmQgPSByZWFkT0dSKCdkb3dubG9hZC9yZV9zY2h1bHN0YW5kLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpIS09fMjAxNSA9IHJlYWRPR1IoJ291dHB1dC9IS09fMjAxNS5nZW9qc29uJywgJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKSAlPiUgY2JpbmQob3ZlcihyZV9zY2h1bHN0YW5kLCBsb3IpKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEtpbmRlciBpbSBCZXppcmsgYXVmIEJsw7Zja2UgaG9jaHJlY2huZW4KCsOcYmVyIGRpZSBFaW53b2huZXJpbmZvcm1hdGlvbmVuIGluIGBSQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbmAga2FubiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCB2b24gTE9SLUViZW5lIGF1ZiBCbG9ja2ViZW5lIGhvY2hnZXJlY2huZXQgd2VyZGVuLgoKVE9ETzogU3RhdHRkZXNzZW4gbWl0IGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuIG1hY2hlbj8KCiMjIyBQbG90IGRlciA2LUrDpGhyaWdlbiBuYWNoIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgCgpXaXIgdmVyd2VuZGVuIGRhcyBtaXR0ZWwgZGVyIDUtIHVuZCA2LUrDpGhyaWdlbi4KClRPRE8gbmV1ZSBEYXRlbiB2b24gVG9ycmVzPwpUT0RPIFByb2dub3NlPwoKYGBge3J9CnJlbGV2YW50X2V3ciA9IGVpbndvaG5lcl9sb3IgJT4lCiAgc2VsZWN0KFJBVU1JRCwgRV9FMDVfMDYsIEVfRTA2XzA3KSAlPiUKICBmaWx0ZXIoUkFVTUlEICVpbiUgcmVsZXZhbnRfbG9ycyRQTFIpICU+JQogICMgU2Nobml0dCBkZXIgNSB1bmQgNi1Kw6RocmlnZW4KICBtdXRhdGUoa2lkcz0oYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSthcy5udW1lcmljKGdzdWIoJywnLCcuJyxFX0UwNV8wNikpKS8yKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhID0gdGlkeShsb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdQTFInKSAlPiUgaW5uZXJfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ2lkJz0nUkFVTUlEJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIyBQbG90IGRlciBFaW53b2huZXIgYXVmIEJsb2NrZWJlbmUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihkZl9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoRWludz1pZmVsc2UoRWludz09MCwgTkEsIEVpbncpKQowCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPUVpbncpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgojIyMgSG9jaHJyZWNobnVuZyBhdWYgQmzDtmNrZSwgU3RydWt0dXJxdW90ZQoKYGBge3J9ClN0cnVrdHVycXVvdGUgPSAwLjkKCmtpZHNfaW5fYmxrcyA9IHJlbGV2YW50X2Jsa3MgJT4lCiAgZ3JvdXBfYnkoUExSKSAlPiUKICBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JQogIHVuZ3JvdXAgJT4lCiAgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JQogIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JQogIG11dGF0ZShraWRzID0gU3RydWt0dXJxdW90ZSpraWRzKSAlPiUgIyBTdHJ1a3R1cnF1b3RlCiAgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JQogIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzCgpgYGB7cn0KVVdCID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX1VXQl9BR0gyMDE2Lmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpVV0IkSUQgPSBwYXN0ZTAoVVdCJEJFWiwgVVdCJFVXQikKc296aW9fVVdCID0gcmVhZF9leGNlbCgnZG93bmxvYWQvRExfQkVfQUgyMDE2X1N0cnVrdHVyZGF0ZW4ueGxzeCcsIHNoZWV0ID0gMykgJT4lIHNlbGVjdChJRCwgc2diSUl1NjU9YEVpbndvaG5lciB1bnRlciA2NSBpbiBTR0IgSUkgMjAxNCBQcm96ZW50YCkKVVdCID0gVVdCICU+JSBzcDo6bWVyZ2Uoc296aW9fVVdCLCBieT0nSUQnKQoKZ2dwbG90KGJyb29tOjp0aWR5KFVXQiwgcmVnaW9uPSdJRCcpICU+JSBpbm5lcl9qb2luKFVXQiAlPiUgYXMuZGF0YS5mcmFtZSwgYnk9YygnaWQnPSdJRCcpKSkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPXNnYklJdTY1KSkgKyBjb29yZF9tYXAoKQpgYGAKCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXQpnSXNWYWxpZChibGtfaW5fYmV6KQpibGtfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aCA9IC0wLjEpCmFueSh4b3IoZ0ludGVyc2VjdHMoYmxrX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aChibGtfaW5fYmV6KSkgPT0gMSkpCnBsb3QoYmxrX2luX2JleikKI2dJbnRlcnNlY3RzKFVXQltVV0IkQkVaID09ICcwNycsXSwgYnlpZD1UUlVFKQpgYGAKCmBgYHtyfQp1d2JfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGVhX3Byb2plY3Rpb24pLCBieWlkPVQsIHdpZHRoPS0wLjEpCmdJc1ZhbGlkKHV3Yl9pbl9iZXopCmFueSh4b3IoZ0ludGVyc2VjdHModXdiX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aCh1d2JfaW5fYmV6KSkgPT0gMSkpCnBsb3QodXdiX2luX2JleikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uID0gZ0ludGVyc2VjdGlvbih1d2JfaW5fYmV6LCBibGtfaW5fYmV6LCBieWlkID0gVCwgZHJvcF9sb3dlcl90ZCA9IFQpCgpnSXNWYWxpZChpbnRlcnNlY3Rpb24pCnBsb3QoaW50ZXJzZWN0aW9uKQpgYGAKCmBgYHtyfQppbnRlcnNlY3Rpb25fdXdiX2RhdGEgPSBpbnRlcnNlY3Rpb24gJT4lIG92ZXIodXdiX2luX2JleikKaW50ZXJzZWN0aW9uX2Jsa19kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKGJsa19pbl9iZXopCmludGVyc2VjdGlvbl9hcmVhID0gZ0FyZWEoaW50ZXJzZWN0aW9uLCBieWlkPVQpCmludGVyc2VjdGlvbl9kYXRhID0gY2JpbmQoCiAgICBpbnRlcnNlY3Rpb25fdXdiX2RhdGEgJT4lIHNlbGVjdChVV0JfSUQ9SUQsIHNnYklJdTY1KSwKICAgIGludGVyc2VjdGlvbl9ibGtfZGF0YSAlPiUgc2VsZWN0KEJMSywgQkxLX0Vpbnc9RWludyksCiAgICBkYXRhLmZyYW1lKGFyZWE9aW50ZXJzZWN0aW9uX2FyZWEpICMgRklYTUUgaG93IGVsc2UgdG8gbm9ybWFsaXplPwogICAgKQpgYGAKCmBgYHtyfQpsZW5ndGgoaW50ZXJzZWN0aW9uKQpucm93KGJsa19pbl9iZXopCiMgZGlkIHdlIG1pc3MgYW55IGJsb2Nrcz8Kc2V0ZGlmZihibGtfaW5fYmV6JEJMSywgaW50ZXJzZWN0aW9uX2RhdGEkQkxLKQpgYGAKClBybyBCbG9jayBtaXNjaGUgZGllIFNHQi1XZXJ0ZSBkZXIgVW50ZXJibMO2Y2tlIGdld2ljaHRldCBuYWNoIEZsw6RjaGUuCkZJWE1FIC0gYmVzc2VyIG5hY2ggQW56YWhsIGRlciBXb2huaMOkdXNlcj8KYGBge3J9CnNnYklJX2JsayA9IGludGVyc2VjdGlvbl9kYXRhICU+JQogIGdyb3VwX2J5KEJMSykgJT4lCiAgc3VtbWFyaXNlKHNnYklJdTY1PXN1bShzZ2JJSXU2NSphcmVhKS9zdW0oYXJlYSkvMTAwKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNnYklJX2JsaywgYnk9YygnaWQnPSdCTEsnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgojIyBBZGphemVueiB2b24gQmzDtmNrZW4KCmBgYHtyfQpyb3cubmFtZXMoYmxrX2luX2JleikgPSBibGtfaW5fYmV6JEJMSwpidWZmZWRlZF9ibGtfaW5fYmV6ID0gZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKQphZGphY2VuY3kgPSBnSW50ZXJzZWN0cyhnQnVmZmVyKGJsa19pbl9iZXosIGJ5aWQ9VCwgd2lkdGg9NDApLCBieWlkID0gVCkKcm93bmFtZXMoYWRqYWNlbmN5KSA9IGJsa19pbl9iZXokQkxLCmNvbG5hbWVzKGFkamFjZW5jeSkgPSBibGtfaW5fYmV6JEJMSwoKYWRqYWNlbmN5X2RmID0gYWRqYWNlbmN5ICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSAlPiUKICBnYXRoZXIodG8sIGNvbm5lY3RlZCwgLWZyb20pICU+JSBmaWx0ZXIoY29ubmVjdGVkKSAlPiUKICBpbm5lcl9qb2luKHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSAlPiUgY29vcmRpbmF0ZXMoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUoZnJvbV9sb25nPVYxLCBmcm9tX2xhdD1WMikgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSkgJT4lCiAgaW5uZXJfam9pbihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCkgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKHRvX2xvbmc9VjEsIHRvX2xhdD1WMikgJT4lIG11dGF0ZSh0bz1yb3duYW1lcyguKSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J2dyYXknLCBkYXRhPWJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSkgKwogIGdlb21fc2VnbWVudChhZXMoeD1mcm9tX2xvbmcsIHk9ZnJvbV9sYXQsIHhlbmQ9dG9fbG9uZywgeWVuZD10b19sYXQpLCBzaXplPTAuMSwgY29sb3I9J2JsYWNrJywgZGF0YT1hZGphY2VuY3lfZGYpICsKICB0aGVtZV9ub3RoaW5nKCkgKyBjb29yZF9tYXAoKQoKZ2dzYXZlKCdmaWdzL2FkamFjZW5jeS5wZGYnKQphZGphY2VuY3lfZGYgJT4lIGZpbHRlcihjb25uZWN0ZWQgJiBmcm9tICE9IHRvKSAlPiUgc2VsZWN0KGZyb20sIHRvKSAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9hZGphY2VuY3kuY3N2JykKYGBgCgoKIyMgU2VsZWN0IHJlbGV2YW50IGRhdGEKCmBgYHtyfQpvcHRpbV9rYXBhcyA9IHJlbGV2YW50X2thcGFzCm9wdGltX2tpZHNfaW5fYmxrcyA9IGtpZHNfaW5fYmxrcyAlPiUgZmlsdGVyKGtpZHMgPiAwKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfbWF0cml4LCBieT0nQkxLJykgJT4lIHNlbGVjdChCTEssIGtpZHMpICU+JSBtdXRhdGUoa2lkcz1raWRzKQpucm93KG9wdGltX2tpZHNfaW5fYmxrcykKbnJvdyhvcHRpbV9rYXBhcykKCnNlbGVjdF9zY2hvb2xzID0gYXMuY2hhcmFjdGVyKG9wdGltX2thcGFzJFNjaHVsbnVtbWVyKQpzZWxlY3RfYmxrcyA9IGFzLmNoYXJhY3RlcihvcHRpbV9raWRzX2luX2Jsa3MkQkxLKQoKb3B0aW1fbWF0cml4ID0gaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKVtzZWxlY3Rfc2Nob29sc10KCmRpbShvcHRpbV9tYXRyaXgpCgpvcHRpbV9rYXBhcyRLYXBhICU+JSBzdW0Kb3B0aW1fa2lkc19pbl9ibGtzJGtpZHMgJT4lIHN1bQpgYGAKCiMjIE5haXZlIFp1b3JkbnVuZzogSmVkZXIgQmxvY2sgenVyIG7DpGNoc3RlbiBTY2h1bGUKCmBgYHtyfQpzb2x1dGlvbiA9IG9wdGltX21hdHJpeCAlPiUgbXV0YXRlKEJMSz1vcHRpbV9raWRzX2luX2Jsa3MkQkxLKSAlPiUgZ2F0aGVyKHNjaG9vbCwgZGlzdCwgLUJMSykgJT4lIGdyb3VwX2J5KEJMSykgJT4lIHRvcF9uKDEsIC1kaXN0KSAlPiUgdW5ncm91cAoKb3B0aW1fbWF0cml4ICU+JSB0ICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzdW1tYXJpc2VfZWFjaChmdW5zKG1pbikpICU+JSBzdW0oKQoKc29saW5lcyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSAlPiUgaW5uZXJfam9pbihjYmluZChhcy5kYXRhLmZyYW1lKGNvb3JkaW5hdGVzKGJsa1tibGskQkVaID09IGJlel9pZCxdKSksIGJsa1tibGskQkVaID09IGJlel9pZCxdQGRhdGFbJ0JMSyddKSkKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4oc29sdXRpb24sIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCwgZGFya2VuID0gYygwLjUsICd3aGl0ZScpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2Nob29sKSwgZGF0YT1kYXRhKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PVYxLHk9VjIseGVuZD1sb24seWVuZD1sYXQpLCBkYXRhPXNvbGluZXMsIHNpemU9MC4zKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQpLCBjb2xvcj0nYmxhY2snLCBzaXplPTIsIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9c3BhdGlhbF9uYW1lKSwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBndWlkZXMoY29sb3I9RiwgZmlsbD1GKQpgYGAKCiMjIERhcnN0ZWxsdW5nIGRlciBadW9yZG51bmcgYWxzIFRhYmVsbGUKCmBgYHtyfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQpgYGAKCmBgYHtyfQpzb2x1dGlvbiAlPiUgaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIGJ5PSdCTEsnKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnLCAnc2Nob29sJz0nZHN0JykpICU+JQogIGdyb3VwX2J5KHNjaG9vbCkgJT4lIHN1bW1hcmlzZSgKICAgIGtpZHM9c3VtKGtpZHMpLAogICAgbnVtX2Jsb2Nrcz1uKCksCiAgICBtaW5fZGlzdD1taW4obWluKSwKICAgIGF2Z19kaXN0PW1lYW4oKGtpZHMqYXZnKS9zdW0oa2lkcykpLAogICAgbWF4X2Rpc3Q9bWF4KG1heCkKICApICU+JQogIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ3NjaG9vbCc9J1NjaHVsbnVtbWVyJykpICU+JQogIG11dGF0ZSgKICAgIHV0aWxpemF0aW9uPWtpZHMvS2FwYQogICkgJT4lIHNlbGVjdCgKICAgU2NodWxlPXNjaG9vbCwKICAgYEJsw7Zja2VgPW51bV9ibG9ja3MsCiAgIEthcGF6aXTDpHQ9S2FwYSwKICAgS2luZGVyPWtpZHMsCiAgIEF1c2xhc3R1bmc9dXRpbGl6YXRpb24sCiAgIGBXZWcgKG1pbilgPW1pbl9kaXN0LAogICBgV2VnICjDmClgPWF2Z19kaXN0LAogICBgV2VnIChtYXgpYD1tYXhfZGlzdAogICkgJT4lCiAgZm9ybWF0dGFibGUoCiAgICBsaXN0KAogICAgICBLaW5kZXIgPSBmb3JtYXR0ZXIoInNwYW4iLCB4IH4gZGlnaXRzKHgsIDIpKSwKICAgICAgQXVzbGFzdHVuZyA9IGZvcm1hdHRlcigic3BhbiIsCiAgICAgICAgc3R5bGUgPSB4IH4gc3R5bGUoY29sb3IgPSBpZmVsc2UoeCA8IDEsICJncmVlbiIsICJyZWQiKSksCiAgICAgICAgeCB+IGljb250ZXh0KGlmZWxzZSh4IDwgMSwgIm9rIiwgInJlbW92ZSIpLCBwZXJjZW50KHgpKQogICAgICApLAogICAgICBgV2VnICjDmClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtaW4pYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWF4KWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIikKICAgICkKICApCmBgYAoKCiMjIERhdGVuIGbDvHIgZGllIEFwcCBzcGVpY2hlcm4KCi0gZW50aXRpZXMuZ2VvanNvbgotIGVudGl0aWVzLmNzdgotIHVuaXRzLmdlb2pzb24KLSB1bml0cy5jc3YKLSB3ZWlnaHRzLmNzdgotIGFzc2lnbm1lbnQuY3N2CgojIyMgTmV1ZSBEYXRlbgoKCiMjIyMgRW50aXRpZXMgLyBTY2h1bGVuCgpgYGB7cn0KZW50aXRpZXMgPSBzdWJzZXQocmVfc2NodWxzdGFuZF9kZiwgc3BhdGlhbF9uYW1lICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lCiAgcmVuYW1lKGVudGl0eV9pZCA9IHNwYXRpYWxfbmFtZSkgJT4lCiAgc2VsZWN0KC1nbWxfaWQsIC1zcGF0aWFsX2FsaWFzLCAtc3BhdGlhbF90eXBlKSAlPiUKICBpbm5lcl9qb2luKHJlbmFtZShyZWxldmFudF9rYXBhcywgY2FwYWNpdHk9S2FwYSksIGJ5PWMoJ2VudGl0eV9pZCc9J1NjaHVsbnVtbWVyJykpCmNvb3JkaW5hdGVzKGVudGl0aWVzKSA9IH4gbG9uICsgbGF0CnByb2o0c3RyaW5nKGVudGl0aWVzKSA9IENSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nKQplbnRpdGllcyAlPiUgd3JpdGVPR1IoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nLCBsYXllcj0iZW50aXRpZXMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHM9RikKZW50aXRpZXNAZGF0YSAlPiUgc2VsZWN0KGVudGl0eV9pZCwgY2FwYWNpdHkpICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2VudGl0aWVzLmNzdicpCmBgYAoKIyMjIyBVbml0cyAvIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlCgpgYGB7cn0KdW5pdHMgPSBzdWJzZXQoYmxrLCBCRVogPT0gYmV6X2lkKQoKdW5pdHNAZGF0YSRQTFIgPSBOVUxMCnVuaXRzQGRhdGEkRWludyA9IE5VTEwKdW5pdHNAZGF0YSRCRVogPSBOVUxMCnVuaXRzQGRhdGEkdW5pdF9pZCA9IHVuaXRzQGRhdGEkQkxLCnVuaXRzQGRhdGEkQkxLID0gTlVMTAoKdW5pdHMgPSB1bml0cyAlPiUgc3A6Om1lcmdlKAogIG9wdGltX2tpZHNfaW5fYmxrcyAlPiUKICAgIGxlZnRfam9pbihzZ2JJSV9ibGspICU+JQogICAgc2VsZWN0KHVuaXRfaWQ9QkxLLCBwb3B1bGF0aW9uPWtpZHMsIHNnYklJdTY1KQogICkKCmZpbGUucmVtb3ZlKCdhcHAvZGF0YS91bml0cy5nZW9qc29uJykKdW5pdHMgJT4lIHdyaXRlT0dSKCdhcHAvZGF0YS91bml0cy5nZW9qc29uJywgbGF5ZXI9InVuaXRzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzPUYpCnVuaXRzQGRhdGEgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvdW5pdHMuY3N2JykKYGBgCgojIyMjIFp1b3JkbnVuZwoKYGBge3J9CmFzc2lnbm1lbnQgPSBzb2x1dGlvbiAlPiUgc2VsZWN0KHVuaXRfaWQ9QkxLLCBlbnRpdHlfaWQ9c2Nob29sKQphc3NpZ25tZW50ICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2Fzc2lnbm1lbnQuY3N2JykKYGBgCgojIyMjIFdlaWdodHMgLyBTY2h1bHdlZ2UKCmBgYHtyfQp0cmF2ZWxfZnJvbV9ibGtzICU+JQogIHJlbmFtZSh1bml0X2lkPUJMSywgZW50aXR5X2lkPWRzdCkgJT4lCiAgI2dhdGhlcih3ZWlnaHQsIHZhbHVlLCAtdW5pdF9pZCwgLWVudGl0eV9pZCkgJT4lCiAgd3JpdGVfY3N2KCdhcHAvZGF0YS93ZWlnaHRzLmNzdicpCmBgYAoKIyMjIyBadXPDpHR6bGljaGUgRGF0ZW4KCmBgYHtyfQpmaWxlLmNvcHkoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgJ2FwcC9kYXRhL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJykKYGBgCgpgYGB7cn0KYWRkcmVzc2VzX2luX2JleiA9IEhLT18yMDE1WyFpcy5uYShvdmVyKEhLT18yMDE1LCBzdWJzZXQoYmxrLCBCRVogPT0gYmV6X2lkKSkkQkVaKSxjKCdTVE4nLCAnSE5SJyldCm5hbWVzKGFkZHJlc3Nlc19pbl9iZXopID0gYygnc3RyZWV0JywgJ25vJykKYWRkcmVzc2VzX2luX2JlekBkYXRhCgpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvYWRkcmVzc2VzLmdlb2pzb24nKQp3cml0ZU9HUihhZGRyZXNzZXNfaW5fYmV6LCAiYXBwL2RhdGEvYWRkcmVzc2VzLmdlb2pzb24iLCBsYXllcj0iYWRkcmVzc2VzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzID0gRkFMU0UpCmBgYAo=